/**
 * Copyright (c) 2011 eelpo.com
 * All rights reserved.
 */
package com.eelpo.common.query.criteria;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.eelpo.common.query.expression.CompoundCriterion;
import com.eelpo.common.query.expression.JoinExpression;
import com.eelpo.common.query.expression.OrderExpression;
import com.eelpo.common.query.expression.ParameterCriterion;
import com.eelpo.common.query.expression.ResultExpression;
import com.eelpo.common.util.BlankUtils;
import com.eelpo.common.util.StringUtils;

/**
 * <pre>
 * 
 * 抽象的Criteria类
 * 
 * </pre>
 * 
 * @author <a href="mailto:Brian@eelpo.com">汪洪州</a>
 * @since 1.0
 */
public abstract class AbstractCriteria implements Criteria {

	protected String alias;
	protected int maxResults;
	protected int firstResult;
	protected Class<? extends Serializable> entityClass;
	protected List<CompoundCriterion> results = new ArrayList<CompoundCriterion>(2);
	protected List<CompoundCriterion> joins = new ArrayList<CompoundCriterion>(2);
	protected List<ParameterCriterion> conditionals = new ArrayList<ParameterCriterion>(4);
	protected List<CompoundCriterion> orders = new ArrayList<CompoundCriterion>(1);
	protected Map<String, Object> hints = new HashMap<String, Object>(1);

	public AbstractCriteria(Class<? extends Serializable> entityClass) {
		this.entityClass = entityClass;
	}

	public AbstractCriteria(Class<? extends Serializable> entityClass, String alias) {
		this.alias = alias;
		this.entityClass = entityClass;
	}

	@Override
	public Criteria alias(String alias) {
		this.alias = alias;
		return this;
	}

	@Override
	public Criteria result(ResultExpression criterion) {
		results.add(criterion);
		return this;
	}

	@Override
	public Criteria add(ParameterCriterion criterion) {
		if (!BlankUtils.isBlank(criterion)) {
			conditionals.add(criterion);
		}
		return this;
	}

	@Override
	public Criteria join(JoinExpression criterion) {
		joins.add(criterion);
		return this;
	}

	@Override
	public Criteria order(OrderExpression criterion) {
		if (!BlankUtils.isBlank(criterion)) {
			orders.add(criterion);
		}
		return this;
	}

	@Override
	public int getFirstResult() {
		return firstResult;
	}

	@Override
	public Criteria setFirstResult(int size) {
		this.firstResult = size;
		return this;
	}

	@Override
	public int getMaxResults() {
		return maxResults;
	}

	@Override
	public Criteria setMaxResults(int size) {
		this.maxResults = size;
		return this;
	}

	@Override
	public boolean isPaging() {
		return maxResults != 0;
	}

	@Override
	public Criteria setHint(String hint, Object value) {
		hints.put(hint, value);
		return this;
	}

	@Override
	public Map<String, Object> getHints() {
		return hints;
	}

	@Override
	public Criteria change(Class<? extends Serializable> entityClass) {
		return change(entityClass, null);
	}

	@Override
	public Criteria change(Class<? extends Serializable> entityClass, String alias) {
		this.alias = alias;
		this.entityClass = entityClass;
		return this;
	}

	@Override
	public Criteria changes(Class<? extends Serializable> entityClass) {
		return changes(entityClass, null);
	}

	@Override
	public Criteria changes(Class<? extends Serializable> entityClass, String alias) {
		clear();
		this.alias = alias;
		this.entityClass = entityClass;
		return this;
	}

	@Override
	public Criteria clear() {
		this.maxResults = 0;
		this.firstResult = 0;
		clearResults();
		clearConditionals();
		clearJoins();
		clearOrders();
		clearHints();
		return this;
	}

	@Override
	public Criteria clearResults() {
		results.clear();
		return this;
	}

	@Override
	public Criteria clearConditionals() {
		conditionals.clear();
		return this;
	}

	@Override
	public Criteria clearJoins() {
		joins.clear();
		return this;
	}

	@Override
	public Criteria clearOrders() {
		orders.clear();
		return this;
	}

	@Override
	public Criteria clearHints() {
		hints.clear();
		return this;
	}

	@Override
	public StringBuilder getJpql(StringBuilder jpql) {

		String aliasPrefix = BlankUtils.isBlank(alias) ? StringUtils.EMPTY : alias.concat(".");

		if (!results.isEmpty()) {
			jpql.append(SELECT);
			for (CompoundCriterion result : results) {
				result.getJpql(jpql, aliasPrefix);
			}
		}

		jpql.append(FROM);
		jpql.append(entityClass.getSimpleName());

		if (!BlankUtils.isBlank(alias)) {
			jpql.append(" AS ");
			jpql.append(alias);
		}

		if (!joins.isEmpty()) {
			for (CompoundCriterion join : joins) {
				join.getJpql(jpql, aliasPrefix);
			}
		}

		if (!conditionals.isEmpty()) {
			jpql.append(WHERE);

			Iterator<ParameterCriterion> iterator = conditionals.iterator();
			ParameterCriterion criterion = iterator.next();
			int index = jpql.length();
			criterion.getJpql(jpql, aliasPrefix);
			jpql.delete(index, index + 4);

			while (iterator.hasNext()) {
				ParameterCriterion conditional = iterator.next();
				conditional.getJpql(jpql, aliasPrefix);
			}
		}

		if (!orders.isEmpty()) {
			jpql.append(ORDERBY);
			Iterator<CompoundCriterion> iterator = orders.iterator();
			while (iterator.hasNext()) {
				iterator.next().getJpql(jpql, aliasPrefix);
				if (iterator.hasNext()) {
					jpql.append(", ");
				}
			}
		}

		return jpql;
	}

	@Override
	public Collection<Object> getParameters(Collection<Object> parameters) {
		if (!conditionals.isEmpty()) {
			for (ParameterCriterion conditional : conditionals) {
				conditional.getParameters(parameters);
			}
		}
		return parameters;
	}

}
